Android逆向基础(DEX/ELF文件格式)
The following article is from 编码安全 Author 编码安全
背景
android逆向分析、脱壳破解分析过程中免不了和android的各种文件格式打交道(so、dex、xml、art、oat等等)。Android下的两个最重要的文件是DEX文件和SO文件,下面重点对这两个文件及关联文件进行下文的梳理总结,以此用于温故知新。
DEX文件
DEX它是android虚拟机的可执行字节码文件,我们知道java文件需要经过javac编译成class文件,dx工具会将所有的class文件合并处理最终生成dex文件。
dex文件分为四大部分: DEX文件头,索引结构区,data数据区,静态链接数据区。
在dex文件中所有的代码和数据都放在data数据区中,索引结构区中存放的是data中各种数据的对应的偏移和索引。
ODEX文件
ODEX它的英文全称为Optimized DEX;即优化过的DEX。
在android5.0之前,Android采用的是JIT(just-in-time)即时编译,也就是程序边执行边编译。为了增加程序执行的效率,android在APK第一次安装的时候将程序的dex文件进行优化生成odex文件,并将其放在了/data/dalvik-cache目录下,等待下次apk运行时直接加载这个目录中经过优化的odex文件(优化基于当前系统的dalvik虚拟机版本,不同版本上的odex文件无法进行兼容),避免重复验证提高执行效率,加快APK的响应时间。
OAT文件
在android5.0之后,android使用的是AOT(Ahead-of-time)事前编译,也就是程序在运行前先编译。oat是ART虚拟机运行的文件,是ELF格式二进制文件,包含DEX和编译的本地机器指令,oat文件包含DEX文件,因此比ODEX文件占用空间更大。
程序在首次安装的时候,dex2oat默认会把classes.dex翻译成本地机器指令,生成ELF格式的OAT文件,并将其放在了/data/dalvik-cache或者是/data/app/packagename/目录下。ART加载OAT文件后不需要经过处理就可以直接运行,它在编译时就从字节码装换成机器码了,因此运行速度更快。
在android5.0之后oat文件的后缀还是odex,但是已经不是android5.0之前的文件格式,而是ELF格式封装的本地机器码,可以认为oat在dex上加了一层壳,可以从oat里提取出dex。
(elf格式的oat)
因为此时的oat文件是一个标准的elf文件,识别其实其是不是oat文件的标准就是看其符号表。
oatdata指向的是ELF文件的.rodata节区,存放了OAT文件头OATHeader,OAT的DEX文件头,原始DEX文件的DexFile等信息。
oatexec指向的是ELF文件的.text节区,这里存放的是编译生成的指定平台的二进制代码。
oatlastword指向的是对应oat文件的结尾。
OAT文件大小差不多= dex文件+art文件
vdex文件
android8.0(Android O)之前dex文件嵌入到oat文件本身中,在Android 8.0之后dex2oat将classes.dex优化生成两个文件oat文件(.odex)和vdex文件(.vdex),其中包含APK的未压缩DEX代码,以及一些旨在加快验证速度的元数据。
odex文件中包含了本机代码的OAT
vdex文件包含了原始的DEX文件副本
ART文件
ART虚拟机在执行dex文件时,需要将dex文件中使用的类,字符串等信息转换为自定义的结构。art文件就是保存APK中使用的一些类,字符串等信息的ART内部表示,可以加快APK程序启动的速度。
ELF文件
ELF文件格式提供了两种不同的视角,在汇编器和链接器看来,ELF文件是由Section Header Table描述的一系列Section的集合,而执行一个ELF文件时,在加载器(Loader)看来它是由Program Header Table描述的一系列Segment的集合。
ELF它是 Executable and Linking Format 的缩写,它是android平台上通用的二进制文件格式。在 Android 的 NDK 开发中,几乎都是和 ELF 打交道。
比如:
1、c / c++ 文件编译得到的 .o(或者 .obj)文件就是 ELF 格式的文件;
2、动态库(.so)文件、可执行文件也是 ELF 文件;
3、动态库的字符串擦除、动态库加壳、动态库修复等都离不开 ELF;
ELF文件名称中的Executable和 Linking表明 ELF 有两种重要的特性。
1、Executable表示可执行的。ELF 文件将参与程序的执行(Execution)过程。包括二进制程序的运行以及动态库 .so 文件的加载。
2、Linking表示可连接的。ELF 文件参与编译链接过程。
文件加载
Android中Java层通过System.load或System.loadLibrary来加载一个so文件,它的定义在Android源码中的路径
为/libcore/luni/src/main/java/java/lang/System.java,执行流程如下:
加载so的两种方式
1、System.loadLibrary(path),只能加载jniLIbs目录下的so文件,这个的执行流程
1.1、先读取so文件的.init_array段
1.2、执行JNI_OnLoad函数
1.3、JNI_ONLoad是.so文件的初始函数
1.4、最后调用具体的native方法
2、System.load(path),可以加载任意路径下的so
这两种方式最终都会调用Android底层的dlopen来打开so
dlopen用来打开一个动态链接库,并将其装入内存。它的定义在Android源码中的路径为/bionic/linker/dlfcn.cpp,执行流程如下:
So文件的入口为init_array、init_func这些初始化函数。这部分在dlopen的过程中就会执行,再之后的是JNI_Onload方法的调用。这里面可以注册一些本地方法,也可以继续做些变量的初始化等操作。
推荐阅读